﻿using System;
using System.Linq.Expressions;
using Affinity.Core.Exceptions;
using Affinity.Core.Utility;

namespace Affinity.Synthesis.ExpressionCells.Basis
{
    /// <summary>
    /// Expression cell class representing a miscellaneous expression that isn't specifically wrapped by another type of expression cell.
    /// </summary>
    internal abstract class GenericExpressionCell : ExpressionCellBase
    {
        /// <summary>
        /// Constructor accepting the expression to represent.
        /// </summary>
        /// <param name="sourceExpression"></param>
        protected GenericExpressionCell(Expression sourceExpression)
            : base(sourceExpression)
        {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected GenericExpressionCell(GenericExpressionCell existing)
            : base(existing)
        {
        }

        /// <summary>
        /// Factory method creating a GenericExpressionCell for the given "atomized" expression. (An atomized expression is always represented as a LambdaExpression.)
        /// </summary>
        /// <param name="atomicExpression"></param>
        /// <returns></returns>
        public static GenericExpressionCell Create(LambdaExpression atomicExpression)
        {
            switch (atomicExpression.Parameters.Count)
            {
                case 0: return (GenericExpressionCell)Activator.CreateInstance(
                    typeof(GenericExpressionCell<>).MakeGenericType(atomicExpression.ReturnType), atomicExpression);

                case 1: return (GenericExpressionCell)Activator.CreateInstance(
                    typeof(GenericExpressionCell<,>).MakeGenericType(atomicExpression.Parameters[0].Type, atomicExpression.ReturnType), atomicExpression);

                case 2: return (GenericExpressionCell)Activator.CreateInstance(
                    typeof(GenericExpressionCell<,,>).MakeGenericType(atomicExpression.Parameters[0].Type, atomicExpression.Parameters[1].Type, atomicExpression.ReturnType), atomicExpression);

                case 3: return (GenericExpressionCell)Activator.CreateInstance(
                    typeof(GenericExpressionCell<,,,>).MakeGenericType(atomicExpression.Parameters[0].Type, atomicExpression.Parameters[1].Type, atomicExpression.Parameters[2].Type, atomicExpression.ReturnType), atomicExpression);

                default: throw new NotImplementedException();
            }
        }
    }

    /// <summary>
    /// Expression cell representing a LambdaExpression taking no parameters and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    internal class GenericExpressionCell<TResult> : GenericExpressionCell
    {
        /// <summary>
        /// Constructor accepting the "atomized" lambda expression to represent.
        /// </summary>
        /// <param name="atomizedExpression"></param>
        public GenericExpressionCell(Expression atomizedExpression)
            : base(atomizedExpression)
        {
            _CompiledTransformation = (Func<TResult>)Expressions.CompileSync((Expression)atomizedExpression);
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected GenericExpressionCell(GenericExpressionCell<TResult> existing)
            : base(existing)
        {
            _CompiledTransformation = existing._CompiledTransformation;
        }

        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new GenericExpressionCell<TResult>(this);
        }

        /// <summary>
        /// "Calculates" the output of this expression cell by invoking the compiled lambda expression.
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            hasValue = true;

            try
            {
                value = _CompiledTransformation.Invoke();
            }
            catch (Exception ex)
            {
                ex = ExceptionUtil.GetInnermostException(ex);

                throw new ExceptionWrapper(new ExceptionWrapper.Parameters
                {
                    Message = string.Format("Exception encountered while executing expression {0}: {1}.", Description, ex.Message),
                    StackTrace = ex.StackTrace
                });
            }
        }


        private Func<TResult> _CompiledTransformation = null;
    }

    /// <summary>
    /// Expression cell representing a LambdaExpression taking one parameter and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParameter"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class GenericExpressionCell<TParameter, TResult> : GenericExpressionCell
    {
        /// <summary>
        /// Constructor accepting the "atomized" lambda expression to represent.
        /// </summary>
        public GenericExpressionCell(LambdaExpression atomizedExpression)
            : base(atomizedExpression)
        {
            _CompiledTransformation = (Func<TParameter, TResult>)Expressions.CompileSync(atomizedExpression, atomizedExpression.Parameters[0]);
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected GenericExpressionCell(GenericExpressionCell<TParameter, TResult> existing)
            : base(existing)
        {
            _CompiledTransformation = existing._CompiledTransformation;
        }
        
        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new GenericExpressionCell<TParameter, TResult>(this);
        }

        /// <summary>
        /// "Calculates" the output of this expression cell by invoking the compiled lambda expression.
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            hasValue = true;

            try
            {
                value = _CompiledTransformation.Invoke((TParameter)inputs[0]);
            }
            catch (Exception ex)
            {
                ex = ExceptionUtil.GetInnermostException(ex);

                throw new ExceptionWrapper(new ExceptionWrapper.Parameters
                {
                    Message = string.Format("Exception encountered while executing expression {0}: {1}.", Description, ex.Message),
                    StackTrace = ex.StackTrace
                });
            }
        }


        private Func<TParameter, TResult> _CompiledTransformation = null;
    }

    /// <summary>
    /// Expression cell representing a LambdaExpression taking two parameters and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParameter1"></typeparam>
    /// <typeparam name="TParameter2"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class GenericExpressionCell<TParameter1, TParameter2, TResult> : GenericExpressionCell
    {
        /// <summary>
        /// Constructor accepting the "atomized" lambda expression to represent.
        /// </summary>
        public GenericExpressionCell(LambdaExpression atomizedExpression)
            : base(atomizedExpression)
        {
            _CompiledTransformation = (Func<TParameter1, TParameter2, TResult>)Expressions.CompileSync(atomizedExpression, atomizedExpression.Parameters[0], atomizedExpression.Parameters[1]);
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected GenericExpressionCell(GenericExpressionCell<TParameter1, TParameter2, TResult> existing)
            : base(existing)
        {
            _CompiledTransformation = existing._CompiledTransformation;
        }

        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new GenericExpressionCell<TParameter1, TParameter2, TResult>(this);
        }
        
        /// <summary>
        /// "Calculates" the output of this expression cell by invoking the compiled lambda expression.
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            hasValue = true;

            try
            {
                value = _CompiledTransformation.Invoke((TParameter1)inputs[0], (TParameter2)inputs[1]);
            }
            catch (Exception ex)
            {
                ex = ExceptionUtil.GetInnermostException(ex);

                throw new ExceptionWrapper(new ExceptionWrapper.Parameters
                {
                    Message = string.Format("Exception encountered while executing expression {0}: {1}.", Description, ex.Message),
                    StackTrace = ex.StackTrace
                });
            }
        }


        private Func<TParameter1, TParameter2, TResult> _CompiledTransformation = null;
    }

    /// <summary>
    /// Expression cell representing a LambdaExpression taking three parameters and returning a value of type TResult.
    /// </summary>
    internal class GenericExpressionCell<TParameter1, TParameter2, TParameter3, TResult> : GenericExpressionCell
    {
        /// <summary>
        /// Constructor accepting the "atomized" lambda expression to represent.
        /// </summary>
        public GenericExpressionCell(LambdaExpression atomizedExpression)
            : base(atomizedExpression)
        {
            _CompiledTransformation = (Func<TParameter1, TParameter2, TParameter3, TResult>)Expressions.CompileSync(atomizedExpression, atomizedExpression.Parameters[0], atomizedExpression.Parameters[1], atomizedExpression.Parameters[2]);
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected GenericExpressionCell(GenericExpressionCell<TParameter1, TParameter2, TParameter3, TResult> existing)
            : base(existing)
        {
            _CompiledTransformation = existing._CompiledTransformation;
        }
        
        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new GenericExpressionCell<TParameter1, TParameter2, TParameter3, TResult>(this);
        }

        /// <summary>
        /// "Calculates" the output of this expression cell by invoking the compiled lambda expression.
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            hasValue = true;

            try
            {
                value = _CompiledTransformation.Invoke((TParameter1)inputs[0], (TParameter2)inputs[1], (TParameter3)inputs[2]);
            }
            catch (Exception ex)
            {
                ex = ExceptionUtil.GetInnermostException(ex);

                throw new ExceptionWrapper(new ExceptionWrapper.Parameters
                    {
                        Message = string.Format("Exception encountered while executing expression {0}: {1}.", Description, ex.Message),
                        StackTrace = ex.StackTrace
                    });
            }
        }


        private Func<TParameter1, TParameter2, TParameter3, TResult> _CompiledTransformation = null;
    }
}
